package tomasulogui;

import tomasulogui.IssuedInst.INST_TYPE;

public class ReorderBuffer {
  public static final int size = 30;
  int frontQ = 0;
  int rearQ = 0;
  ROBEntry[] buff = new ROBEntry[size];
  int numRetirees = 0;
  

  PipelineSimulator simulator;
  RegisterFile regs;
  boolean halted = false;

  public ReorderBuffer(PipelineSimulator sim, RegisterFile registers) {
    simulator = sim;
    regs = registers;
  }

  public ROBEntry getEntryByTag(int tag) {
    return buff[tag];
  }

  public int getInstPC(int tag) {
    return buff[tag].getInstPC();
  }

  public boolean isHalted() {
    return halted;
  }

  public boolean isFull() {
    return (frontQ == rearQ && buff[frontQ] != null);
  }

  public int getNumRetirees() {
    return numRetirees;
  }

  public boolean retireInst() {
    // 3 cases
    // 1. regular reg dest inst
    // 2. isBranch w/ mispredict
    // 3. isStore
    ROBEntry retiree = buff[frontQ];

    if (retiree == null) {
      return false;
    }

    if (retiree.isHaltOpcode()) {
      halted = true;
      return true;
    }

    boolean shouldAdvance = true;

    // TODO - this is where you look at the type of instruction and
    // figure out how to retire it properly
    if(retiree.isComplete()){
        switch(retiree.getOpcode()){
        //with normal destination register
        case ADD:
        case ADDI:
        case SUB:
        case MUL:
        case DIV:
        case AND:
        case ANDI:
        case OR:
        case ORI:
        case XOR:
        case XORI:
        case SLL:
        case SRL:
        case SRA:
        case LOAD:
            regs.setReg(retiree.getWriteReg(), retiree.getWriteValue());
            break;
        // a store
        case STORE:
            simulator.getMemory().setIntDataAtAddr(retiree.getWriteValue(), retiree.getReg2Value());
            break;
        case JAL:
        case JALR:
            regs.setReg(31, retiree.getInstPC()+4);
        }
        if(retiree.branchMispredicted()){
            //flush ROB and squash insts and change PC
            simulator.squashAllInsts();
            simulator.setPC(retiree.getBranchTgt());
            shouldAdvance = false;
            numRetirees++;
        }
        
        if (shouldAdvance) {
            numRetirees++;
            buff[frontQ] = null;
            frontQ = (frontQ + 1) % size;
        }
    }
    else if(!retiree.isBusy()){
        
    }
    
    // if mispredict branch, won't do normal advance
    
    return false;
  }

  public void readCDB(CDB cdb) {
    // check entire ROB for someone waiting on this data
    // could be destination reg
    // could be store address source

    // TODO body of method
      for(int i = 0; i < size; i++){
          ROBEntry checkEntry = buff[i];
          if(checkEntry != null){
              if(checkEntry.getReg1Tag() == cdb.getDataTag()){
                  checkEntry.setReg1Value(cdb.getDataValue());
                  checkEntry.reg1Ready = true;
              }
              if(checkEntry.getReg2Tag() == cdb.getDataTag()){
                  checkEntry.setReg2Value(cdb.getDataValue());
                  checkEntry.reg2Ready = true;
              }
                if(checkEntry.getOpcode() == INST_TYPE.STORE){
                    if(checkEntry.isReg1Ready() && checkEntry.isReg2Ready()){
                        checkEntry.complete = true;
                        checkEntry.setWriteValue(checkEntry.getReg1Value() + checkEntry.immediate);
                    }
                }
          }
          
      }
  }

  public void updateInstForIssue(IssuedInst inst) {
    // the task is to simply annotate the register fields
    // the dest reg will be assigned a tag, which is just our slot#
    // all src regs will either be assigned a tag, read from reg, or forwarded from ROB

    // TODO - possibly nothing if you use my model
    // I use the call to copyInstData below to do 2 things:
    // 1. update the Issued Inst
    // 2. fill in the ROB entry

    // first get a ROB slot
    if (buff[rearQ] != null) {
      throw new MIPSException("updateInstForIssue: no ROB slot avail");
    }
    ROBEntry newEntry = new ROBEntry(this);
    buff[rearQ] = newEntry;
    newEntry.copyInstData(inst, rearQ);

    rearQ = (rearQ + 1) % size;
  }

  public int getTagForReg(int regNum) {
    return (regs.getSlotForReg(regNum));
  }

  public int getDataForReg(int regNum) {
    return (regs.getReg(regNum));
  }

  public void setTagForReg(int regNum, int tag) {
    regs.setSlotForReg(regNum, tag);
  }
  
  public void flush(){
    frontQ = 0;
    rearQ = 0;
    for(int i=0; i<size; i++){
        buff[i] = null;
    }
  }

  public int findDestReg(int reg){
      int stopPoint = 0;
      if(frontQ == 0){
          stopPoint = size - 1;
      }
      else{
          stopPoint = frontQ - 1;
      }
      int i = rearQ;
      while(i != stopPoint){
          if((buff[i].getWriteReg() == reg)){
              return i;
          }
          i = (i - 1);
          if(i == -1){
              i = size-1;
          }
      }
      return -1;
  }
  
    public int getRearQMinusOne(){
        if(rearQ == 0){
            return size-1;
        }
        return rearQ-1;
    }
    
}

